//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Windows;

namespace OpenLS.Spreadsheet
{
    public partial class WorksheetPane : IWorksheetFragment
    {
        public WorksheetPane(Worksheet worksheet)
        {
            _worksheet = worksheet;
            enableEvents(false);
            _worksheet.IncrementGeneration();
            //\\CheckInvariant();
#if DEBUG
            creationStack = new StackTrace().ToString();
#endif
        }

#if DEBUG
        private string creationStack;
#endif

        public bool IsActive
        {
            get { return Worksheet.ActivePane == this; }
        }

        public double? SplitWidth
        {
            get
            {
                if (Worksheet.Pane == null)
                    return null;
                switch (PaneType)
                {
                    case PaneType.TopLeft:
                    case PaneType.BottomLeft:
                        return Worksheet.Pane.Width;
                }
                return null;
            }
        }

        public double? DesiredWidth
        {
            get
            {
                if (SplitWidth != null)
                    return SplitWidth;

                switch (PaneType)
                {
                    case PaneType.BottomRight:
                    case PaneType.TopRight:
                        break;
                    case PaneType.TopLeft:
                    case PaneType.BottomLeft:
                        Pane p = _worksheet.Pane;
                        if (p == null)
                            break;
                        if (p.ColumnCount!= null)
                        {
                            int count = p.ColumnCount.Value;
                            double result = 0;
                            for (int i = Left; i <  count; i++)
                            {
                                result += Worksheet.Columns[i].ActualWidth;
                            }
                            return result;
                        }
                        return null;
                }
                return null;
            }
        }

        public double? DesiredHeight
        {
            get
            {
                if (SplitHeight != null)
                    return SplitHeight;
                switch (PaneType)
                {
                    case PaneType.BottomRight:
                    case PaneType.BottomLeft:
                        break;
                    case PaneType.TopLeft:
                    case PaneType.TopRight:
                        Pane p = _worksheet.Pane;
                        if (p == null)
                            break;
                        if (p.RowCount!= null)
                        {
                            int count = p.RowCount.Value;
                            double result = 0;
                            for (int i = Top; i < count; i++)
                            {
                                result += Worksheet.Rows[i].ActualHeight;
                            }
                            return result;
                        }
                        return null;
                }
                return null;
            }
        }

        public double? SplitHeight
        {
            get
            {
                if (Worksheet.Pane == null)
                    return null;
                switch (PaneType)
                {
                    case PaneType.TopLeft:
                    case PaneType.TopRight:
                        return Worksheet.Pane.Height;
                }
                return null;
            }
        }

        internal PaneType PaneType
        {
            get
            {
                if (Worksheet.TopLeftPane == this)
                    return PaneType.TopLeft;
                if (_worksheet.TopRightPane == this)
                    return PaneType.TopRight;
                if (_worksheet.BottomLeftPane == this)
                    return PaneType.BottomLeft;
                if (_worksheet.BottomRightPane == this)
                    return PaneType.BottomRight;
                throw new NotSupportedException();
            }
        }

        internal EventHandler LayoutChanged;

        private bool _eventsEnabled;

        internal void EnableEvents()
        {
            enableEvents(true);
        }

        private void enableEvents(bool withRecompute)
        {
            if (_eventsEnabled)
            {
                Debug.WriteLine("Only setup once");
                return;
            }
            _eventsEnabled = true;
            if (withRecompute)
                recompute(true);
            _worksheet.NonVolatileCells.CollectionChanged += cellsCollectionChanged;
            _worksheet.LayoutChanged += layoutChanged;
            _worksheet.Columns.LayoutChanged += layoutChanged;
            _worksheet.Columns.CollectionChanged += columnsCollectionChanged;
            _worksheet.Rows.LayoutChanged += layoutChanged;
            _worksheet.Rows.CollectionChanged += rowsCollectionChanged;
        }

        internal void DisableEvents()
        {
            if (!_eventsEnabled)
            {
                Debug.WriteLine("Only setup once");
                return;
            }
            _eventsEnabled = false;
            //\\this.Recompute(true);
            _worksheet.NonVolatileCells.CollectionChanged -= cellsCollectionChanged;
            _worksheet.LayoutChanged -= layoutChanged;
            _worksheet.Columns.LayoutChanged -= layoutChanged;
            _worksheet.Columns.CollectionChanged -= columnsCollectionChanged;
            _worksheet.Rows.LayoutChanged -= layoutChanged;
            _worksheet.Rows.CollectionChanged -= rowsCollectionChanged;
        }

        private readonly Worksheet _worksheet;

        public Worksheet Worksheet
        {
            get { return _worksheet; }
        }


        private void layoutChanged(object sender, EventArgs e)
        {
            if (LayoutChanged != null)
                LayoutChanged(this, e);
            if (_disabler != null)
            {
                _disabler.LayoutChanged();
                return;
            }
            OnLayoutChanged();
        }

        private void OnLayoutChanged()
        {
            recompute(true);
            Window.VerticalChange(Top, Bottom);
            Window.HorizontalChange(Left, Right);
            NotifyPropertyChanged("DesiredWidth");
            NotifyPropertyChanged("DesiredHeight");
            NotifyPropertyChanged("LeftOffset");
            NotifyPropertyChanged("TopOffset");
        }

        private void columnsCollectionChanged(object sender, EventArgs e)
        {
            clear();
            recompute(true);
        }

        private void clear()
        {
            Columns.Clear();
            Rows.Clear();
        }

        private void rowsCollectionChanged(object sender, EventArgs e)
        {
            clear();
            recompute(true);
        }

        private void recompute(bool forceRowColumns)
        {
#if DEBUG
            Contract.Requires(ValidCells);
            Contract.Requires(Enumerable.Any(Worksheet.GetPanes(), p => p == this));
#endif
#if DEBUG
            CheckInvariant();
#endif
            if (_disabler != null)
            {
                _disabler.Recompute(forceRowColumns);
                return;
            }
            int columnIndex = Left;
            double w = Worksheet.Columns[columnIndex].ActualWidth;
            double thisWidth = Width;
            int maxColumn = Worksheet.MaxColumnIndex;
            if (ColumnIndex == 0 && this.Worksheet.TopRightPane != null && this.Worksheet.Pane.ColumnCount!= null)
                maxColumn = Worksheet.Pane.ColumnCount.Value-1;
            int maxRow = Worksheet.MaxRowIndex;
            if (RowIndex == 0 && this.Worksheet.BottomLeftPane != null && this.Worksheet.Pane.RowCount!= null)
                maxRow = Worksheet.Pane.RowCount.Value-1;
            while (w < thisWidth && columnIndex < maxColumn)
            {
                columnIndex++;
                w += _worksheet.Columns[columnIndex].ActualWidth;
            }
           // Contract.Assert(columnIndex < maxColumn);
            if (columnIndex == Worksheet.MaxColumnIndex)
            {
                int left = Left;
                while (columnIndex >= left)
                {
                    Column column = _worksheet.Columns[columnIndex];
                    if (!(column.ActualWidth > 0))
                    {
                        columnIndex--;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            int rowIndex = Top;
            double h = Worksheet.Rows[rowIndex].ActualHeight;
            while (h < _height && rowIndex < maxRow)
            {
                rowIndex++;
                h += _worksheet.Rows[rowIndex].ActualHeight;
            }
            if (rowIndex == Worksheet.MaxRowIndex)
            {
                int top = Top;
                while (columnIndex >= top)
                {
                    Row row = _worksheet.Rows[rowIndex];
                    if (!(row.ActualHeight > 0))
                    {
                        rowIndex--;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            RowSpan = rowIndex - Top + 1;
#if DEBUG
          //  if (this.PaneType == PaneType.TopLeft && this.Worksheet.TopRightPane != null)
            //    Contract.Assert(columnIndex < this.Worksheet.TopRightPane.Left);
CheckInvariant();
#endif
            ColumnSpan = columnIndex - Left + 1;
            NotifyPropertyChanged("Right");
            //NotifyPropertyChanged("Left");
            NotifyPropertyChanged("MaxRow");
            NotifyPropertyChanged("MaxColumn");
            NotifyPropertyChanged("ViewportHeight");
            NotifyPropertyChanged("ViewportWidth");
            NotifyPropertyChanged("ColumnCount");
            NotifyPropertyChanged("RowCount");

            if (RowSpan == Rows.Count && ColumnSpan == Columns.Count && !forceRowColumns)
                return;

            bool changed = false;
            if (Rows.Count == 0 || Rows[0].Index >= Bottom || Rows[Rows.Count - 1].Index <= Top)
            {
                changed = true;
                //\\while (Rows.Count > 0)
                //\\Rows.RemoveAt(0);
                Rows.Clear();
                for (int i = Top; i <= rowIndex; i++)
                    Rows.Add(Worksheet.Rows[i]);
            }
            else
            {
                int oldTop = Rows[0].Index;
                switch (Top.CompareTo(oldTop))
                {
                    case -1:
                        for (int i = oldTop - 1; i >= Top; i--)
                            Rows.Insert(0, Worksheet.Rows[i]);
                        changed = true;
                        break;
                    case 1:
                        for (int i = Top; i > oldTop; i--)
                            Rows.RemoveAt(0);
                        changed = true;
                        break;
                }
                int oldMaxRow = Rows[Rows.Count - 1].Index;
                switch (Bottom.CompareTo(oldMaxRow))
                {
                    case -1:
                        for (int i = Bottom; i < oldMaxRow; i++)
                            Rows.RemoveAt(Rows.Count - 1);
                        changed = true;
                        break;
                    case 1:
                        for (int i = oldMaxRow + 1; i <= Bottom; i++)
                            Rows.Add(_worksheet.Rows[i]);
                        changed = true;
                        break;
                }
            }

            if (Columns.Count == 0 || _columns[0].Index >= Right || Columns[Columns.Count - 1].Index <= Left)
            {
                changed = true;
                Columns.Clear();
                for (int i = Left; i <= columnIndex; i++)
                    Columns.Add(Worksheet.Columns[i]);
            }
            else
            {
                int oldLeft = Columns[0].Index;
                switch (Left.CompareTo(oldLeft))
                {
                    case -1:
                        for (int i = oldLeft - 1; i >= Left; i--)
                            Columns.Insert(0, Worksheet.Columns[i]);
                        changed = true;
                        break;
                    case 1:
                        for (int i = Left; i > oldLeft; i--)
                            Columns.RemoveAt(0);
                        changed = true;
                        break;
                }
                int oldMaxColumn = Columns[Columns.Count - 1].Index;
                switch (Right.CompareTo(oldMaxColumn))
                {
                    case -1:
                        for (int i = Right; i < oldMaxColumn; i++)
                            Columns.RemoveAt(Columns.Count - 1);
                        changed = true;
                        break;
                    case 1:
                        for (int i = oldMaxColumn + 1; i <= Right; i++)
                            if (_worksheet.Columns.ValidIndex(i))
                                Columns.Add(_worksheet.Columns[i]);
                        changed = true;
                        break;
                }
            }
            if (changed)
                computeCells(columnIndex, rowIndex);
            _worksheet.IncrementGeneration();
#if DEBUG
            CheckInvariant();
#endif
        }


        private void computeCells(int columnIndex, int rowIndex)
        {
#if DEBUG
            Contract.Requires(ValidCells);
            Contract.Ensures(ValidCells);
#endif
            if (cellsRecomputeDisabled)
                return;

            var oldCells = new Dictionary<Cell, Cell>();
            var toRemove = new List<int>();
            int index = 0;
            /* foreach (Cell cell in Cells)
            {
                if (Contains(cell))
                    oldCells.Add(cell, cell);
                else
                    toRemove.Add(index);
                index++;
            }
            toRemove.Sort();
            toRemove.Reverse();
            foreach (int i in toRemove)
                Cells.RemoveAt(i);

            //\\Cells.Clear();
            Range r = Range.Create(this.Worksheet,
                                   new RangeAddress(new CellAddress(Top, Left), new CellAddress(rowIndex, columnIndex)), false); 
            //new MultiCellsRange(Worksheet[Top, Left], Worksheet[rowIndex, columnIndex]);
            foreach (Cell cell in r.GetNonEmptyCells())
            {
                if (oldCells.ContainsKey(cell))
                    continue;
                Cells.Add(cell);
            }*/
        }


        private void cellsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
#if DEBUG
            Contract.Requires(ValidCells);
            Contract.Ensures(ValidCells);
#endif
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    /*foreach (Cell c in e.NewItems)
                    {
                       //\\ Contract.Assert(!Cells.Contains(c));
#if DEBUG
                        Contract.Assert(!c.IsDeleted);
                        if (c.IsDeleted)
                            throw new Exception();
#endif
                       //\\ if (Contains(c))
                         //\\   Cells.Add(c);
                    }*/
                    break;
                case NotifyCollectionChangedAction.Remove:
                    /*foreach (Cell c in e.OldItems)
                    {
                        if (Contains(c))
                            Cells.Remove(c);
                    }*/
                    break;
                case NotifyCollectionChangedAction.Reset:
                    //\\Cells.Clear();
                    recompute(true);
                    computeCells(Left, Top);
                    break;
                case NotifyCollectionChangedAction.Move:

                case NotifyCollectionChangedAction.Replace:
                    throw new NotImplementedException();
            }
        }


        private readonly ObservableCollection<Cell> _cells = new ObservableCollection<Cell>();
#if DEBUG
        public bool ValidCells
        {
            get { return _cells.Count == Enumerable.Count(Enumerable.Distinct(_cells)); }
        }

        [ContractInvariantMethod]
        private void Invariant2()
        {
            Contract.Invariant(ValidCells);
        }
#endif

        internal CellsWindow _window;

        internal CellsWindow Window
        {
            get
            {
                Contract.Ensures(Contract.Result<CellsWindow>() != null);
                if (_window == null)
                {
                    _window = new CellsWindow(Worksheet,
                                              new RangeAddress(new CellAddress(Top, Left),
                                                               new CellAddress(Bottom, Right)));
                }
//                _worksheet.IncrementGeneration();
                return _window;
            }
        }

        private readonly ObservableCollection<Column> _columns = new ObservableCollection<Column>();

        public ObservableCollection<Column> Columns
        {
            get { return _columns; }
        }

#if DEBUG
        [System.Diagnostics.Contracts.ContractInvariantMethod]
        private void Invariant()
        {
            Contract.Invariant(Enumerable.Max(Enumerable.Select(Columns, c => c.Index), 0) <= Right);
            Contract.Invariant(this.Worksheet.TopRightPane == null || this.Worksheet.TopLeftPane == null || this.Worksheet.BottomLeftPane == null || this.Worksheet.BottomRightPane == null || this.PaneType != PaneType.TopLeft || this.Worksheet.TopRightPane == null || this.Right < Worksheet.TopRightPane.Left || this.Worksheet.Pane == null || this.Worksheet.Pane.State != PaneState.Frozen);
        }
        private void CheckInvariant()
        {
            //\\Contract.Assert(Enumerable.Max(Enumerable.Select(Columns, c => c.Index), 0) <= Right);
            //\\Contract.Assert(this.Worksheet.TopRightPane == null || this.Worksheet.TopLeftPane == null || this.Worksheet.BottomLeftPane == null || this.Worksheet.BottomRightPane == null || this.PaneType != PaneType.TopLeft || this.Worksheet.TopRightPane == null || this.Right < Worksheet.TopRightPane.Left || this.Worksheet.Pane == null || this.Worksheet.Pane.State != PaneState.Frozen);
        }
#endif
        private readonly ObservableCollection<Row> _rows = new ObservableCollection<Row>();

        public ObservableCollection<Row> Rows
        {
            get { return _rows; }
        }

        public bool Contains(Cell c)
        {
            Contract.Requires(c != null);
            return contains(c.Address);
        }

        private bool contains(CellAddress c)
        {
            if (c.Row < Top)
                return false;
            if (c.Column < Left)
                return false;
            if (c.Row > Bottom)
                return false;
            if (c.Column > Right)
                return false;
            return true;
        }

        internal RangeAddress RangeAddress
        {
            get { return new RangeAddress(new CellAddress(Top, Left), new CellAddress(Bottom, Right)); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        internal void NotifyPropertyChanged(string propertyName)
        {
            Contract.Requires(
                GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) !=
                null);
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            switch (propertyName)
            {
                case "Top":
                case "Left":
                    notifyOthers(propertyName);
                    break;
            }
            switch (propertyName)
            {
                case "Left":
                case "Top":
                case "Right":
                case "Bottom":
                case "Width":
                case "Height":
                    Worksheet.IncrementGeneration();
                    break;
            }
        }

        public double LeftOffset
        {
            get { return Worksheet.Columns[Left].Offset; }
        }

        public double TopOffset
        {
            get { return Worksheet.Rows[Top].Offset; }
        }

        private bool _inChange;

        private int RowIndex
        {
            get
            {
                switch (PaneType)
                {
                    case PaneType.TopLeft:
                    case PaneType.TopRight:
                        return 0;
                    case PaneType.BottomLeft:
                    case PaneType.BottomRight:
                        return 1;
                }
                throw new NotSupportedException();
            }
        }

        private int ColumnIndex
        {
            get
            {
                switch (PaneType)
                {
                    case PaneType.TopLeft:
                    case PaneType.BottomLeft:
                        return 0;
                    case PaneType.TopRight:
                    case PaneType.BottomRight:
                        return 1;
                }
                throw new NotSupportedException();
            }
        }

        private void notifyOthers(string propertyName)
        {
            if (_inChange)
                return;
            _inChange = true;
            foreach (WorksheetPane pane in Enumerable.Where(Worksheet.GetPanes(), p => p != this))
            {
                if (pane == this)
                    continue;
                if (propertyName == "Top" && pane.RowIndex == RowIndex)
                    pane.Top = Top;
                if (propertyName == "Left" && pane.ColumnIndex == ColumnIndex)
                    pane.Left = Left;
            }
            _inChange = false;
        }

        #endregion

        private CellAddress _topLeft;
        private int ColumnSpan { get {Contract.Ensures(Contract.Result<int>() > 0); return _columnSpan; }
            set
            {
                Contract.Requires(value > 0); _columnSpan = value;
            } }
        private int RowSpan { get { Contract.Ensures(Contract.Result<int>() > 0); return _rowSpan; } set { Contract.Requires(value > 0); _rowSpan = value; } }

        public int Left
        {
            get { return _topLeft.Column; }
            set
            {
                if (value < MinLeft)
                    throw new ArgumentOutOfRangeException("value");
                if (value > MaxLeft)
                    return;
                if (Left == value)
                    return;
                _topLeft = new CellAddress(_topLeft.Row, value);
                recompute(true);

#if DEBUG
                checkConsistent();
#endif
                Window.HorizontalChange(Left, Right);

                NotifyPropertyChanged("Left");
                NotifyPropertyChanged("LeftOffset");
            }
        }

        //[Obsolete]
        private const bool cellsRecomputeDisabled = true; //\\ for transition


#if DEBUG
        private void checkConsistent()
        {
            if (Worksheet.TopRightPane == this)
            {
                WorksheetPane topLeft = Worksheet.TopLeftPane;
                if (topLeft.Columns.Count == 0)
                    return;
                Column lastColumn = topLeft.Columns[topLeft.Columns.Count - 1];
                //\\       Contract.Assert(this.Left >= topLeft.MaxColumn);
                //\\     Contract.Assert(this.Left > lastColumn.Index);
            }
        }
#endif

        public int Top
        {
            get { return _topLeft.Row; }
            set
            {
                Contract.Requires(value >= 0);
                Contract.Requires(value <= Worksheet.MaxRowIndex);
                Contract.Ensures(Top == value || value > MaxTop);
                if (value < MinTop)
                    throw new ArgumentOutOfRangeException("value");
                if (value > MaxTop)
                    return;
                Contract.Assert(value >= MinTop);
                int oldValue = Top;
                if (oldValue == value)
                    return;

                _topLeft = new CellAddress(value, _topLeft.Column);
                recompute(true);
                Window.VerticalChange(Top, Bottom);
                NotifyPropertyChanged("Top");
                NotifyPropertyChanged("TopOffset");
            }
        }

        public int MaxTop
        {
            get
            {
                switch (this.PaneType)
                {
                    case PaneType.BottomRight:
                    case PaneType.BottomLeft:
                        return int.MaxValue;
                }
                var pane = this.Worksheet.Pane;
                if (pane == null)
                    return int.MaxValue;
                if (pane.State != PaneState.Frozen)
                    return int.MaxValue;
                if (pane.RowCount!= null)
                    return 0;
                return int.MaxValue;
  
            }
        }

        public int Right
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);
                Contract.Ensures(Contract.Result<int>() <= Worksheet.MaxColumnIndex);
                Contract.Ensures(Contract.Result<int>() >= Left);
                return Math.Min(_topLeft.Column + ColumnSpan - 1, Worksheet.MaxColumnIndex);
            }
        }

        public int Bottom
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);
                Contract.Ensures(Contract.Result<int>() <= Worksheet.MaxRowIndex);
                Contract.Ensures(Contract.Result<int>() >= Top);
                return Math.Min(_topLeft.Row + RowSpan - 1, Worksheet.MaxRowIndex);
            }
        }

        private double _width;

        public double Width
        {
            get { return _width; }
            set
            {
                if (_width == value)
                    return;
                _width = value;
                recompute(false);
                Window.HorizontalChange(Left, Right);
                NotifyPropertyChanged("Width");
            }
        }

        private double _height;
        private int _commentGeneration = -1;
        private ObservableCollection<Comment> _comments;
        private int _columnSpan = 1;
        private int _rowSpan = 1;

        private IEnumerable<Comment> GetCommentsImp()
        {
            IWorksheetFragment f = this;
            foreach (Cell cell in f.GetCells())
            {
                if (cell.Comment != null)
                    yield return cell.Comment;
            }
        }

        internal ObservableCollection<Comment> Comments()
        {
            {
                int generation = Worksheet.Generation;
                if (_commentGeneration >= generation)
                {
                    Contract.Assert(_comments != null);
                    return _comments;
                }
                var comments = new Dictionary<Comment, bool>();
                if (_comments == null)
                {
                    _comments = new ObservableCollection<Comment>();
                    foreach (var item in GetCommentsImp())
                        _comments.Add(item);
                }
                else
                {
                    foreach (Comment c in _comments)
                    {
                        comments.Add(c, false);
                    }
                    var toAdd = new List<Comment>();
                    foreach (Comment c in GetCommentsImp())
                    {
                        if (comments.ContainsKey(c))
                            comments.Remove(c);
                        else
                        {
                            toAdd.Add(c);
                        }
                    }
                    foreach (Comment c in comments.Keys)
                        _comments.Remove(c);
                    foreach (Comment c in toAdd)
                        _comments.Add(c);
                }
                _commentGeneration = generation;
                return _comments;
            }
        }

        public double Height
        {
            get { return _height; }
            set
            {
                if (_height == value)
                    return;
                _height = value;
                recompute(false);
                Window.VerticalChange(Top, Bottom);
                NotifyPropertyChanged("Height");
            }
        }

#if DEBUG
        [Conditional("DEBUG")]
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            //\\  Contract.Invariant(Worksheet.Columns.HasCollectionChanged());
            //  int index = int.MaxValue;
            // foreach (Column column in Columns)
            //    Debug.Assert(!column.IsDeleted);
            //  foreach (Column column in Columns)
            {
                //    if (index != int.MaxValue)
                //      Debug.Assert(column.Index == index + 1);
                //  index = column.Index;
            }
        }
#endif
        internal void EnsureVisible (CellAddress topLeft, CellAddress bottomRight)
        {
            if (!topLeft.Equals(bottomRight))
            {
                double w = 0;
                double h = 0;
                int column;
                for (column = topLeft.Column; column < bottomRight.Column; column++)
                {
                    w += _worksheet.Columns[column].ActualWidth;
                    if (w > Width)
                        break;
                }


                int row;
                for (row = topLeft.Row; row < bottomRight.Row; row++)
                {
                    h += _worksheet.Rows.GetActualHeight(row);
                    if (h > Height)
                        break;
                }
                bottomRight = new CellAddress(row, column);
                EnsureVisible(bottomRight);
                if (!topLeft.Equals(bottomRight))
                    EnsureVisible(topLeft);
            }
            else
            {
                EnsureVisible(topLeft);
            }
        }

        internal void EnsureVisibleColumn(CellAddress a)
        {
            if (a.Column < Left)
            {
                if (!isLockedAtZero(Direction.Column))
                    Left = Math.Max(a.Column, getMinimumValue(Direction.Column));
            }
            double xa = Worksheet.Columns[a.Column].RightOffset;
            double x0 = _worksheet.Columns[Left].Offset;
            int left = Left;
            while (xa - x0 > Width)
            {
                x0 += _worksheet.Columns[left].ActualWidth;
                left++;
                if (left > Worksheet.MaxColumnIndex)
                {
                    left = Worksheet.MaxColumnIndex;
                    break;
                }
            }
            if (!isLockedAtZero(Direction.Column))
            {
                Left = Math.Max(left, getMinimumValue(Direction.Column));
            }
            if (a.Column < Left)
            {
                if (!isLockedAtZero(Direction.Column))
                    Left = Math.Max(a.Column, getMinimumValue(Direction.Column));
            }
        }
        internal void EnsureVisibleRow(CellAddress a)
        {
            if (a.Row < Top)
            {
                if (!isLockedAtZero(Direction.Row))
                    Top = Math.Max(a.Row, getMinimumValue(Direction.Row));
            }
            double ya = _worksheet.Rows[a.Row].BottomOffset;
            double y0 = _worksheet.Rows[Top].Offset;
            int top = Top;
            while (ya - y0 > Height)
            {
                y0 += _worksheet.Rows.GetActualHeight(top);
                top++;
                if (top > Worksheet.MaxRowIndex)
                {
                    top = Worksheet.MaxRowIndex;
                    break;
                }
            }

            if (!isLockedAtZero(Direction.Row))
            {
                Contract.Assert(top <= Worksheet.MaxRowIndex);
                Top = Math.Max(top, getMinimumValue(Direction.Row));
            }
            if (a.Row < Top)
            {
                if (!isLockedAtZero(Direction.Row))
                    Top = Math.Max(a.Row, getMinimumValue(Direction.Row));
            }
        }
        internal void EnsureVisible(CellAddress a)
        {
            if (a.Column < Left)
            {
                if (!isLockedAtZero(Direction.Column))
                    Left = Math.Max(a.Column, getMinimumValue(Direction.Column));
            }
            if (a.Row < Top)
            {
                if (!isLockedAtZero(Direction.Row))
                    Top = Math.Max(a.Row, getMinimumValue(Direction.Row));
            }
            double xa = Worksheet.Columns[a.Column].RightOffset;
            double ya = _worksheet.Rows[a.Row].BottomOffset;
            double x0 = _worksheet.Columns[Left].Offset;
            double y0 = _worksheet.Rows[Top].Offset;
            int left = Left;
            while (xa - x0 > Width)
            {
                x0 += _worksheet.Columns[left].ActualWidth;
                left++;
                if (left > Worksheet.MaxColumnIndex)
                {
                    left = Worksheet.MaxColumnIndex;
                    break;
                }
            }
            if (!isLockedAtZero(Direction.Column))
            {
                Left = Math.Max(left, getMinimumValue(Direction.Column));
            }
            int top = Top;
            while (ya - y0 > Height)
            {
                y0 += _worksheet.Rows.GetActualHeight(top);
                top++;
                if (top > Worksheet.MaxRowIndex)
                {
                    top = Worksheet.MaxRowIndex;
                    break;
                }
            }

            if (!isLockedAtZero(Direction.Row))
            {
                Contract.Assert(top <= Worksheet.MaxRowIndex);
                Top = Math.Max(top, getMinimumValue(Direction.Row));
            }
            if (a.Column < Left)
            {
                if (!isLockedAtZero(Direction.Column))
                    Left = Math.Max(a.Column, getMinimumValue(Direction.Column));
            }
            if (a.Row < Top)
            {
                if (!isLockedAtZero(Direction.Row))
                    Top = Math.Max(a.Row, getMinimumValue(Direction.Row));
            }
        }

        private enum Direction
        {
            Row,
            Column
        } ;

        /// <summary>
        /// Gets the minimum value of Top
        /// </summary>
        public int MinTop
        {
            get
            {
                return getMinimumValue(Direction.Row);
                ;
            }
        }

        /// <summary>
        /// Gets the minimum value of Left
        /// </summary>
        public int MinLeft
        {
            get { return getMinimumValue(Direction.Column); }
        }
        /// <summary>
        /// Gets the maximum value of Left
        /// </summary>
        public int MaxLeft
        {
            get {
                switch (this.PaneType)
                {
                    case PaneType.BottomRight:
                    case PaneType.TopRight:
                        return int.MaxValue;
                }
                var pane = this.Worksheet.Pane;
                if (pane == null)
                    return int.MaxValue;
                if (pane.State != PaneState.Frozen)
                    return int.MaxValue;
                if (pane.ColumnCount != null)
                    return 0;
                return int.MaxValue;
            }
        }

        private int getMinimumValue(Direction direction)
        {
            Pane pane = Worksheet.Pane;
            if (pane == null)
                return 0;
            if (pane.State == PaneState.Split)
                return 0;
            switch (direction)
            {
                case Direction.Row:
                    if (RowIndex == 1)
                        return _worksheet.Pane.RowCount.Value;
                    return 0;
                case Direction.Column:
                    if (ColumnIndex == 1)
                        return _worksheet.Pane.ColumnCount.Value;
                    return 0;
                default:
                    throw new NotImplementedException();
            }
        }

        private bool isLockedAtZero(Direction direction)
        {
            Pane pane = Worksheet.Pane;
            if (pane == null)
                return false;
            if (pane.State == PaneState.Split)
                return false;
            switch (direction)
            {
                case Direction.Row:
                    return RowIndex == 0 && Worksheet.BottomLeftPane != null;
                case Direction.Column:
                    return ColumnIndex == 0 && Worksheet.TopRightPane != null;
                default:
                    throw new NotSupportedException();
            }
        }

        internal void EnsureVisible(Cell a)
        {
            EnsureVisible(a.Address, a.GetBottomRight());
        }

        internal void EnsureVisible(Range a)
        {
            EnsureVisible(a.TopLeftCell.Address, a[a.RowSpan - 1, a.ColumnSpan - 1].GetBottomRight());
        }

        public void Resize(Size size)
        {
            if (size.Width == Width && size.Height == Height)
                return;
            _width = size.Width;

            _height = size.Height;
            recompute(false);
            Window.HorizontalChange(Left, Right);
            Window.VerticalChange(Top, Bottom);
            NotifyPropertyChanged("Width");
            NotifyPropertyChanged("Height");
        }

        public int RowCount
        {
            get
            {
                int result = RowSpan - 1;
                result = Math.Max(result, 1);
           //     return this.Rows[Bottom].BottomOffset/ (Math.Max(Height, 1));

                return result;
            }
        }

        public int ColumnCount
        {
            get
            {
                int result = ColumnSpan - 1;
                result = Math.Max(result, 1);
          //      return this.Columns[Right].RightOffset/(Math.Max(Width, 1));
                return result;
            }
        }
        public double ViewportWidth
        {
            get
            {
                double v = Worksheet.Columns[MaxColumnPosition].RightOffset / (Math.Max(Width, 1));
                if (v == 0)
                    return 1;
                return (this.MaxColumnPosition - MinColumn) /  v;



            }
        }
        internal int MinColumn
        {
            get
            {
                if (Worksheet.TopRightPane == null)
                    return 0;
                if (Worksheet.View.Pane == null)
                    return 0;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return 0;
                return Worksheet.TopRightPane.MinLeft;
            }
        }
        internal int MaxColumnPosition
        {
            get
            {
             //   if (!IsFrozen)
                {
                    var maxColumn = Math.Max(Worksheet.BottomRight.Column, Worksheet.ScrollPane.Right);
                    var leftColumn =
                        this.Worksheet.Columns.GetIndexForOffset(Worksheet.Columns[maxColumn].RightOffset - Width);
                    leftColumn = Math.Max(leftColumn, MinColumn);
                      return leftColumn + 1;
                }
               //     return Math.Max(Math.Max(Worksheet.BottomRight.Column, Worksheet.ScrollPane.Right) - Worksheet.ScrollPane.ColumnCount, Worksheet.ScrollPane.MinLeft + 1);
                return Math.Max(Worksheet.ScrollPane.MaxColumn, MinColumn + 1);
            }
        }
        private bool IsFrozen
        {
            get
            {
                if (Worksheet.BottomLeftPane == null && Worksheet.TopRightPane == null)
                    return false;
                if (Worksheet.View.Pane == null)
                    return false;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return false;
                return true;
            }
        }
        public double ViewportHeight
        {
            get
            {
                double v = this.Worksheet.Rows[MaxRowPosition].BottomOffset/ (Math.Max(Height, 1));
                if (v == 0)
                    return 1;
                return (this.MaxRowPosition - MinRow) / v;




            }
        }

        internal int MaxRowPosition
        {
            get
            {
             //   if (!IsFrozen)
                {
                    var maxRow = Math.Max(Worksheet.BottomRight.Row, Worksheet.ScrollPane.Bottom);
                    var lastRow =
                        this.Worksheet.Rows.GetIndexForOffset(Worksheet.Rows[maxRow].BottomOffset - Height);
                    lastRow = Math.Max(lastRow, MinRow);
                    return lastRow + 1;
                } return Math.Max(Worksheet.ScrollPane.MaxRow, MinRow + 1);
            }
        }

        int MinRow
        {
            get
            {
                if (Worksheet.BottomLeftPane == null)
                    return 0;
                if (Worksheet.View.Pane == null)
                    return 0;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return 0;
                return Worksheet.BottomLeftPane.MinTop;
            }
        }


        public int MaxRow
        {
            get
            {
                try
                {
                    return Math.Max(Bottom, _worksheet.Bottom) + 1 - RowCount;
                }
                catch (Exception)
                {
                }
                return Bottom + 1;
            }
        }

        public int MaxColumn
        {
            get
            {
                try
                {
                    return Math.Max(Right, _worksheet.Right) + 1 - ColumnCount;
                }
                catch (Exception)
                {
                }
                return Right + 1;
            }
        }

        internal Cell TopLeftCell
        {
            get { return Worksheet[Top, Left]; }
        }


        public CellAddress GetCellAddressAt(Point pt)
        {
            int row = Worksheet.Rows.GetIndexForOffset(Math.Max(pt.Y + Worksheet.Rows[Top].Offset, 0));
            if (row < 0)
                Contract.Assert(row >= 0);
            return new CellAddress(
                row,
                Worksheet.Columns.GetIndexForOffset(Math.Max(pt.X + Worksheet.Columns[Left].Offset, 0)));
        }

        internal CellAddress GetCellClosestAddressAt(Point pt)
        {
            CellAddress result = GetCellAddressAt(pt);
            double x1 = Worksheet.Columns.GetOffset(result.Column) - Worksheet.Rows.GetOffset(Left);
            double x2 = Worksheet.Columns.GetOffset(result.Column + 1) - Worksheet.Rows.GetOffset(Left);
            if (pt.X - x1 > x2 - pt.X)
                result = new CellAddress(result.Row, result.Column + 1);
            double y1 = Worksheet.Rows.GetOffset(result.Row) - Worksheet.Rows.GetOffset(Top);
            double y2 = Worksheet.Rows.GetOffset(result.Row + 1) - Worksheet.Rows.GetOffset(Top);
            if (pt.Y - y1 > y2 - pt.Y)
                result = new CellAddress(result.Row + 1, result.Column);
            return result;
        }


        internal void Activate()
        {
            Worksheet.ActivePane = this;
        }


        internal bool Contains2(Cell c)
        {
            if (c.Row.Index < Top)
                return false;
            if (c.Column.Index < Left)
                return false;
            if (c.Row.Index >= Bottom)
                return false;
            if (c.Column.Index >= Right)
                return false;
            return true;
        }

        #region IWorksheetFragment Members

        #endregion

        #region IWorksheetFragment Members

        #endregion

        #region IWorksheetFragment Members

        IEnumerable<Column> IWorksheetFragment.GetColumns()
        {
            return Columns;
        }

        IEnumerable<Row> IWorksheetFragment.GetRows()
        {
            return Rows;
        }

        IEnumerable<Cell> IWorksheetFragment.GetCells()
        {
            return Window.GetCells();
        }

        #endregion

        internal void RefreshWindow()
        {
            _window = null;
        }
    }
}